package main

import (
	"container/list"
	"fmt"
	"math/rand"
	"time"

	"github.com/jan-bar/golibs"
)

const (
	LengthX  = 20 /* 矩阵行数 */
	LengthY  = 40 /* 矩阵列数 */
	NoneFlag = 0  /* 空白标识 */
	FoodFlag = 1  /* 食物标识 */
	BodyFlag = 2  /* 蛇身标识 */
)

var (
	MatrixData [LengthX][LengthY]byte /* 0:表示空位,1表示食物,2表示蛇 */
	api        *golibs.Win32Api
)

/**
* 初始化
**/
func init() {
	api = golibs.NewWin32Api()
	api.Clear()                        /* 清屏 */
	rand.Seed(time.Now().UnixNano())   /* 初始化随机数种子 */
	api.SetWindowText("贪吃蛇")           /* 设置标题 */
	api.CenterWindowOnScreen(550, 400) /* 居中显示,并设置窗口大小 */
	api.ShowHideCursor(false)          /* 隐藏光标 */

	var i int
	api.GotoXY(1, 0)
	for i = 0; i < LengthY; i++ {
		fmt.Print("-") /* 画顶部横线 */
	}
	api.GotoXY(1, LengthX+1)
	for i = 0; i < LengthY; i++ {
		fmt.Print("-") /* 画底部横线 */
	}

	for i = 1; i <= LengthX; i++ {
		api.GotoXY(0, i)
		fmt.Print("|") /* 画左边竖线 */
		api.GotoXY(LengthY+1, i)
		fmt.Print("|") /* 画右边竖线 */
	}
}

/**
* 主程序
**/
func main() {
	var (
		headPos         = golibs.Coord{LengthX / 2, LengthY / 2}
		dirNew    int32 = golibs.KeyUp
		dirOld    int32 = golibs.KeyUp
		direction       = make(chan int32)
		tLoop           = time.NewTimer(time.Second)
		Snake           = list.New() /* 创建蛇身链表 */
		score           = 0          /* 吃一个食物加1分 */
	)
	DrawSnake(headPos, BodyFlag)
	Snake.PushFront(headPos) /* 链表存储蛇头位置 */
	RandFood()               /* 初始一个食物 */
	PrintScore(score)        /* 打印初始分数 */

	go func() {
		for { /* 此处只往通道丢获取的按键值 */
			direction <- golibs.WaitKeyBoard()
		}
	}()

	for {
		headPos = Snake.Front().Value.(golibs.Coord) /* 每次取蛇头位置 */

		select {
		case dir, ok := <-direction:
			tLoop.Stop() /* 避免改变方向时定时的循环也移动一下蛇身 */
			if ok {
				dirNew = dir /* 得到新的方向 */
			}
		case <-tLoop.C: /* 这里,每秒刷新游戏 */
		}
		tLoop.Reset(time.Second) /* 重置间隔时间 */

		switch dirNew {
		case golibs.KeyUp: /* 新方向朝上 */
			if dirOld == golibs.KeyDown {
				dirNew = golibs.KeyDown
				headPos.X++ /* 旧方向如果朝下,则继续向下走 */
			} else { /* 旧方向为: 左右上 */
				dirOld = golibs.KeyUp
				headPos.X--
			}
		case golibs.KeyDown:
			if dirOld == golibs.KeyUp {
				dirNew = golibs.KeyUp
				headPos.X-- /* 旧方向如果朝上,则继续向上走 */
			} else { /* 旧方向为: 左右下 */
				dirOld = golibs.KeyDown
				headPos.X++
			}
		case golibs.KeyLeft:
			if dirOld == golibs.KeyRight {
				dirNew = golibs.KeyRight
				headPos.Y++ /* 旧方向如果朝右,则继续向右走 */
			} else { /* 旧方向为: 上下左 */
				dirOld = golibs.KeyLeft
				headPos.Y--
			}
		case golibs.KeyRight:
			if dirOld == golibs.KeyLeft {
				dirNew = golibs.KeyLeft
				headPos.Y-- /* 旧方向如果朝左,则继续向左走 */
			} else { /* 旧方向为: 上下右 */
				dirOld = golibs.KeyRight
				headPos.Y++
			}
		}

		if headPos.X < 0 || headPos.Y < 0 || headPos.X >= LengthX || headPos.Y >= LengthY || MatrixData[headPos.X][headPos.Y] == BodyFlag {
			api.GotoXY(LengthY+3, 7)
			fmt.Print("你输了,按回车退出!")
			break /* 越界了,这个位置是蛇身,游戏结束 */
		}

		if MatrixData[headPos.X][headPos.Y] == NoneFlag {
			var end = Snake.Back()                        /* 如果头部为空白,则尾部也要去掉一个 */
			DrawSnake(end.Value.(golibs.Coord), NoneFlag) /* 将蛇尾置位为空白 */
			Snake.Remove(end)                             /* 下一步是空白,尾部删除一个蛇尾 */
		} else { /* 吃到一个食物,则需要再初始化一个食物 */
			score++ /* 吃一个食物加1分 */
			PrintScore(score)
			RandFood()
		}
		Snake.PushFront(headPos) /* 头部加入链表 */
		DrawSnake(headPos, BodyFlag)
	}

	fmt.Scanln() /* 避免一闪而逝 */
}

/**
* 打印分数
**/
func PrintScore(score int) {
	api.GotoXY(LengthY+3, 5)
	fmt.Print("分数:", score)
}

/**
* 在指定位置画整个界面数据
* 包括空白,蛇,食物
**/
func DrawSnake(pos golibs.Coord, dType byte) {
	MatrixData[pos.X][pos.Y] = dType /* 标记这个位置的类型 */

	api.GotoXY(pos.Y+1, pos.X+1)
	switch dType {
	case NoneFlag:
		fmt.Print(" ")
	case BodyFlag:
		fmt.Print("#")
	case FoodFlag:
		fmt.Print("+")
	}
}

/**
* 在空白位置
* 随机画一个食物
**/
func RandFood() {
	var i, j int
	for {
		i = rand.Intn(LengthX) /* 随机一个X值 */
		j = rand.Intn(LengthY) /* 随机一个Y值 */
		if NoneFlag == MatrixData[i][j] {
			DrawSnake(golibs.Coord{i, j}, FoodFlag)
			break /* 空白位置画一个食物 */
		}
	}
}

/** 下面是对链表库的解释
func (e *Element) Next() *Element  //返回该元素的下一个元素，如果没有下一个元素则返回nil
func (e *Element) Prev() *Element//返回该元素的前一个元素，如果没有前一个元素则返回nil。
func New() *List //返回一个初始化的list
func (l *List) Back() *Element //获取list l的最后一个元素
func (l *List) Front() *Element //获取list l的第一个元素
func (l *List) Init() *List  //list l初始化或者清除list l
func (l *List) InsertAfter(v interface{}, mark *Element) *Element  //在list l中元素mark之后插入一个值为v的元素，并返回该元素，如果mark不是list中元素，则list不改变。
func (l *List) InsertBefore(v interface{}, mark *Element) *Element//在list l中元素mark之前插入一个值为v的元素，并返回该元素，如果mark不是list中元素，则list不改变。
func (l *List) Len() int //获取list l的长度
func (l *List) MoveAfter(e, mark *Element)  //将元素e移动到元素mark之后，如果元素e或者mark不属于list l，或者e==mark，则list l不改变。
func (l *List) MoveBefore(e, mark *Element)//将元素e移动到元素mark之前，如果元素e或者mark不属于list l，或者e==mark，则list l不改变。
func (l *List) MoveToBack(e *Element)//将元素e移动到list l的末尾，如果e不属于list l，则list不改变。
func (l *List) MoveToFront(e *Element)//将元素e移动到list l的首部，如果e不属于list l，则list不改变。
func (l *List) PushBack(v interface{}) *Element//在list l的末尾插入值为v的元素，并返回该元素。
func (l *List) PushBackList(other *List)//在list l的尾部插入另外一个list，其中l和other可以相等。
func (l *List) PushFront(v interface{}) *Element//在list l的首部插入值为v的元素，并返回该元素。
func (l *List) PushFrontList(other *List)//在list l的首部插入另外一个list，其中l和other可以相等。
func (l *List) Remove(e *Element) interface{}//如果元素e属于list l，将其从list中删除，并返回元素e的值。
**/
